/*
 *	Copyright (C) Ergobyte Informatics S.A. - All Rights Reserved
 *
 * 	This material is proprietary to Ergobyte. The methods and techniques described herein are considered trade secrets
 *	and/or confidential. Reproduction or distribution, in whole or in part, is forbidden except by express written permission.
 */
package com.ergobyte.servo.client;

import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base32;

public class TotpGenerator {

	// Constants

	private static final Base32 BASE_32 = new Base32();
	private static final int INTERVAL = 30 * 1000; // 30 seconds
	private static final int PN_LENGTH = 6; // 6 digits
	private static final String PN_FORMAT = String.format("%%0%dd", PN_LENGTH);
	private static final int PN_MODULO = (int)Math.pow(10, PN_LENGTH);

	// Fields

	private Mac messageSigner;

	// Constructors

	public TotpGenerator(String secret) {
		byte[] keyBytes = BASE_32.decode(secret);
		try {
			this.messageSigner = Mac.getInstance("HMACSHA1");
			this.messageSigner.init(new SecretKeySpec(keyBytes, ""));
		}
		catch(GeneralSecurityException e) {
			throw new RuntimeException(String.format("Error seting up time-base OTP generator: %s", e.getMessage()), e);
		}
	}

	// Public methods

	public String generateToken(long challengeAsLong) {
		if(challengeAsLong == -1l) challengeAsLong = System.currentTimeMillis() / INTERVAL;
		byte[] challenge = ByteBuffer.allocate(8).putLong(challengeAsLong).array();
		byte[] messageSignature = messageSigner.doFinal(challenge);
		return String.format(PN_FORMAT, getPassNumber(messageSignature));
	}

	// Internal stuff

	// Create a Google Authenticator compatible pass number
	private int getPassNumber(byte[] signature) {
		int offset = signature[signature.length - 1] & 0xF;
		int passNumber = 0;
		passNumber |= (signature[offset++] & 0xff) << 24;
		passNumber |= (signature[offset++] & 0xff) << 16;
		passNumber |= (signature[offset++] & 0xff) <<  8;
		passNumber |= (signature[offset  ] & 0xff);
		return (passNumber & 0x7FFFFFFF) % PN_MODULO;
	}
}
